﻿using System;
using System.Reflection;
using System.IO;

using ServerASISTSYS.Exceptions;

namespace ServerASISTSYS
{
    class Filter : IFilter
    {
        private Assembly   _a                    = null;
        private Type       _filter               = null;
        private MethodInfo _get_id               = null;
        private MethodInfo _get_version          = null;
        private MethodInfo _get_description      = null;
        private MethodInfo _parse                = null;
        private MethodInfo _validate             = null;
        private MethodInfo _set_server_functions = null;
        private MethodInfo _can_process          = null;
        private object     _instance             = null;

        private string     path;
        private string     dllName;

        private Filter     next                  = null;

        // This is used to remember ServerFunctions instance until it is needed
        public ServerFunctions sf { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="path">path to DLL</param>
        /// <param name="dllName">DLL name</param>
        public Filter(string path, string dllName)
        {
            this.path = path;
            this.dllName = dllName;
        }

        #region INTERFACE_METHODS
        ///////////////////////////////////////////////////////////////////////
        public int GetID()
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            return (int)_get_id.Invoke(_instance, null);
        }

        public string GetVersion()
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            return (string)_get_version.Invoke(_instance, null);
        }

        public string GetDescription()
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            return (string)_get_description.Invoke(_instance, null);
        }

        public void Parse(int length, int id, byte[] data) 
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            object[] param = { length, id, data };
            _parse.Invoke(_instance, param);
        }

        public bool Validate()
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            return (bool)_validate.Invoke(_instance, null);
        }

        public void SetServerFunctions(object sf)
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            object[] param = { sf };
            _set_server_functions.Invoke(_instance, param);
        }

        public bool CanProcess(int id)
        {
            if (_a == null) // if the dll was not loaded call LazyInit
                LazyInit();

            if (_filter != null)
            {
                object[] param = { id };
                return (bool)_can_process.Invoke(_instance, param);
            }
            else
                return false;
        }
        ///////////////////////////////////////////////////////////////////////
        #endregion

        /// <summary>
        /// Part of Lazy Load Pattern.
        /// </summary>
        private void LazyInit()
        {
            _a = Assembly.LoadFile(path + dllName + ".dll");

            /* Name convention:
             * The class name is "Filter" and the namespace is the name of the DLL
             **/
            _filter = _a.GetType(dllName + "." + "Filter");
            if (_filter == null)
                return;

            // Load the methods;
            _get_id = _filter.GetMethod("GetID");
            _get_version = _filter.GetMethod("GetVersion");
            _get_description = _filter.GetMethod("GetDescription");
            _parse = _filter.GetMethod("Parse");
            _validate = _filter.GetMethod("Validate");
            _set_server_functions = _filter.GetMethod("SetServerFunctions");
            _can_process = _filter.GetMethod("CanProcess");

            // Create a new instance of the class
            _instance = Activator.CreateInstance(_filter);
        }

        /// <summary>
        /// It setts the next Filter in the chain.
        /// Used for Chain Of Responsability Pattern.
        /// </summary>
        /// <param name="f">the next Filter</param>
        /// <returns>the next Filter (the filter send by parameter)</returns>
        public Filter SetNext(Filter f)
        {
            next = f;
            return f;
        }

        public void ProcessMessage(int length, int ID, byte[] data)
        {
            if (CanProcess(ID))
            {
                this.SetServerFunctions(sf);
                this.Parse(length, ID, data);
                this.Validate();
            }
            else
            {
                if (next != null)
                    next.ProcessMessage(length, ID, data);
                else
                    throw new UnknownMessageID(ID);
            }
        }
    }
}
